package com.hyny.framework.platform.system.aspect;


import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SimplePropertyPreFilter;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.hyny.framework.platform.system.annotation.FieldPower;
import com.hyny.framework.platform.system.annotation.Log;
import com.hyny.framework.platform.system.entity.dto.LoginRoleFieldPowerDTO;
import com.hyny.framework.platform.system.entity.pojo.SysOperateLog;
import com.hyny.framework.platform.system.service.SysOperateLogService;
import com.hyny.framework.platform.system.utils.FieldPowerUtil;
import com.hyny.framework.platform.system.utils.SecurityUtil;
import com.hyny.framework.platform.system.utils.bean.APClassMethodInfo;
import com.hyny.framework.platform.system.utils.bean.Result;
import com.hyny.framework.utils.IpUtil;
import com.hyny.framework.utils.LogUtil;
import com.hyny.framework.utils.WebUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.HandlerMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Slf4j
@Aspect
@Component
public class HttpRequestAspect  {
    @Autowired
    private SysOperateLogService sysOperateLogServices;
    /**
     * 定义切点
     */
    @Pointcut(value = "execution(* com.hyny.framework.platform.system.controller.*.*(..))")
    public void cutOffPoint() {
    }

    @Pointcut(value = "execution(* com.hyny.framework.platform.biz.controller.*.*(..))")
    public void cutOffPointBiz() {
    }


    @Pointcut(value = "execution(* com.hyny.framework.platform.system.controller.LoginController.*(..))")
    public void noCutOffPoint() {
    }

    @Around("(cutOffPoint()||cutOffPointBiz())&&!noCutOffPoint()")
    public Result doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        TimeInterval timer = DateUtil.timer();

        log.info("AOP---开始");
//        组装请求相关信息
        APClassMethodInfo apClassMethodInfo = getAPClassMethodInfo(joinPoint);
        if (apClassMethodInfo!=null&&apClassMethodInfo.getAlog()!=null&&apClassMethodInfo.getAlog().persisted()) {
//            记录日志信息
            logInfo(apClassMethodInfo);
        }
//        发起请求
        Result result = (Result) joinPoint.proceed();
        if(result!=null){
            result = resultFieldPower(result, joinPoint);
            result.setResultFieldPowerEnum(null);
        }
        log.info("AOP---结束---耗时:{}毫秒---返回结果:{}", timer.interval(), JSONObject.toJSONString(result));
//       清空请求链路信息
        clear();
        return result;
    }

    /**
     * 处理字段权限
     *
     * @param result
     * @return
     */
    public Result resultFieldPower(Result result, ProceedingJoinPoint joinPoint) {
        if (result.getResultFieldPowerEnum() != null) {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            //获取被切入方法对象
            Method method = signature.getMethod();
            //获取方法上的FieldPower注解
            FieldPower fieldPower = method.getAnnotation(FieldPower.class);
            if (fieldPower != null) {
                List<LoginRoleFieldPowerDTO.Field> fieldList = FieldPowerUtil.getFieldList( WebUtil.getRequest().getRequestURI());
                if(fieldList!=null&&fieldList.size()>0){
                    switch (result.getResultFieldPowerEnum()){
                        case R_LIST:
                            return resultFieldPowerList(result,fieldPower,fieldList);
                        case R_PAGE:
                            return resultFieldPowerPage(result,fieldPower,fieldList);
                        case R_OBJECT:
                            return resultFieldPowerObject(result,fieldPower,fieldList);
                    }
                }
            }
            return result;
        } else {
            return result;
        }
    }

    /**
     * 角色字段权限过滤 集合
     *
     * @param result
     * @param fieldPower
     * @param fieldList
     * @return
     */
    public Result resultFieldPowerList(Result result, FieldPower fieldPower, List<LoginRoleFieldPowerDTO.Field> fieldList) {
        result.setData(JSONArray.parseArray(resultFieldPower(result.getData(), fieldPower, fieldList)));
        return result;
    }

    /**
     * 角色字段权限过滤 分页
     *
     * @param result
     * @param fieldPower
     * @param fieldList
     * @return
     */
    public Result resultFieldPowerPage(Result result, FieldPower fieldPower, List<LoginRoleFieldPowerDTO.Field> fieldList) {
        IPage data = (IPage) result.getData();
        data.setRecords(JSONArray.parseArray(resultFieldPower(data.getRecords(), fieldPower, fieldList)));
        result.setData(data);
        return result;
    }


    /**
     * 角色字段权限过滤 对象
     *
     * @param result
     * @param fieldPower
     * @param fieldList
     * @return
     */
    public Result resultFieldPowerObject(Result result, FieldPower fieldPower, List<LoginRoleFieldPowerDTO.Field> fieldList) {
        result.setData(JSONObject.parseObject(resultFieldPower(result.getData(), fieldPower, fieldList)));
        return result;
    }


    /**
     * 角色字段权限过滤
     *
     * @param obj
     * @param fieldPower
     * @param fieldList
     * @return
     */
    public String resultFieldPower(Object obj, FieldPower fieldPower, List<LoginRoleFieldPowerDTO.Field> fieldList) {
        String[] fieldNames = fieldList.stream().map(e -> e.getFieldName()).collect(Collectors.toList()).stream().toArray(String[]::new);
        SimplePropertyPreFilter filter = new SimplePropertyPreFilter(fieldPower.returnType(), fieldNames);
        return JSON.toJSONString(obj, filter);
    }


    /**
     * 获取当前接口字段权限
     *
     * @return
     */



    /**
     * 组装请求相关信息
     *
     * @param joinPoint
     * @return
     */
    public APClassMethodInfo getAPClassMethodInfo(ProceedingJoinPoint joinPoint) {
        //请求类名
        String className = joinPoint.getTarget().getClass().getName();
        //请求方法
        String methodName = joinPoint.getSignature().getName();


        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //获取被切入方法对象
        Method method = signature.getMethod();
        //获取方法上的Log注解
        Log alog = method.getAnnotation(Log.class);
        if (alog != null) {
            if (StrUtil.isNotBlank(alog.value())) {
                LogUtil.setLogPrefix(alog.value());
            }
        }
        String requestParam = "";
        // 获取请求参数
        if (HttpMethod.PUT.name().equals(WebUtil.getRequest().getMethod()) || HttpMethod.POST.name().equals(WebUtil.getRequest().getMethod())) {
            requestParam = argsArrayToString(joinPoint.getArgs());

        } else {
            Map<?, ?> paramsMap = (Map<?, ?>) WebUtil.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
            requestParam = paramsMap.toString();
        }
        APClassMethodInfo apClassMethodInfo = new APClassMethodInfo();
        apClassMethodInfo.setMethodName(methodName);
        apClassMethodInfo.setClassName(className);
        apClassMethodInfo.setAlog(alog);
        apClassMethodInfo.setRequestParam(requestParam);
        return apClassMethodInfo;
    }


    /**
     * 组装日志信息
     *
     * @return
     */
    public void logInfo(APClassMethodInfo apClassMethodInfo) {
        SysOperateLog apOperateLog = new SysOperateLog();
        apOperateLog.setTitle(apClassMethodInfo.getAlog().value());
        apOperateLog.setTranceId(LogUtil.getTraceId());
        apOperateLog.setMethod(apClassMethodInfo.getClassName() + "." + apClassMethodInfo.getMethodName() + "()");
        // 获取请求的地址
        String ip = IpUtil.getIpAddr(WebUtil.getRequest());
        apOperateLog.setRequestIp(ip);
        apOperateLog.setRequestUri(WebUtil.getRequest().getRequestURI());
        apOperateLog.setOperName(SecurityUtil.getUsername());
        apOperateLog.setRequestParam(apClassMethodInfo.getRequestParam());
        apOperateLog.setRequestTime(new Date());
        sysOperateLogServices.save(apOperateLog);

    }


    /**
     * 移除 MDC
     */
    public void clear() {
        LogUtil.removeTraceId();
        LogUtil.removeLogPrefix();
    }


    /**
     * 参数拼装
     */
    private String argsArrayToString(Object[] paramsArray) {
        String params = "";
        if (paramsArray != null && paramsArray.length > 0) {
            for (Object o : paramsArray) {
                if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) {
                    try {
                        Object jsonObj = JSON.toJSON(o);
                        params += jsonObj.toString() + " ";
                    } catch (Exception e) {
                    }
                }
            }
        }
        return params.trim();
    }

    /**
     * 判断是否需要过滤的对象。
     *
     * @param o 对象信息。
     * @return 如果是需要过滤的对象，则返回true；否则返回false。
     */
    private boolean isFilterObject(final Object o) {
        Class<?> clazz = o.getClass();
        if (clazz.isArray()) {
            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
        } else if (Collection.class.isAssignableFrom(clazz)) {
            Collection collection = (Collection) o;
            for (Object value : collection) {
                return value instanceof MultipartFile;
            }
        } else if (Map.class.isAssignableFrom(clazz)) {
            Map map = (Map) o;
            for (Object value : map.entrySet()) {
                Map.Entry entry = (Map.Entry) value;
                return entry.getValue() instanceof MultipartFile;
            }
        }
        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
                || o instanceof BindingResult;
    }


}
